/************************************************************************
                 gmDisplay.cpp - Copyright Claudio Botta

This library is free software; you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or 
(at your option) any later version.
This library is distributed in the hope that it will be useful, but 
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public License 
along with this library; if not, write to the 
Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, 
Boston, MA 02110, USA

**************************************************************************/

#include <iostream>
#include <SDL/SDL.h>
#include "gmDisplay.h"
#include "gmDisplaySetup.h"
#include <GL/glut.h>    
#include <GL/gl.h>	
#include <GL/glu.h>
#include <math.h>	
SDL_Event event;


using namespace std;
using namespace geomagick;

//! the stored ModelView Matrix.
GLdouble modMat[16] = {0,1,0,0,0,0,1,0,0,0,0,0,0,0,1,0};
//! the stored Projection Matrix
GLdouble projMat[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
//! The ViewPort
GLint viewPort[4] = {0,0,0,0};

static GLfloat MatSpec[] = {0.9,0.9,0.9,1.0};
static GLfloat Diffuse[] = {0.7,0.7,0.7,1.0};
static GLfloat MatShininess[] = {0.7};
static GLfloat LightPos[] = {-20.0,1.0,-10.0,0.0};
static GLfloat LightPos1[] = {0.0,-10.0,-10.0,0.0};



void gmGLDisplay::draw()
{
        glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
       	glLoadIdentity();
	
	glLightfv(GL_LIGHT0,GL_AMBIENT, &MatSpec[0]);
	glLightfv(GL_LIGHT0,GL_DIFFUSE, &MatSpec[0]);
        glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
	//glLightfv(GL_LIGHT1, GL_POSITION, LightPos1);
	
	float r = sqrt(rtrx*rtrx+rtry*rtry);
	glTranslatef(0.0,0.0,shift);
	
	glRotatef(rtry,1.0f,0.0f,0.0f);	
	glRotatef(rtrx,0.0f,cos(3.141*rtry/180.0f),sin(3.141*rtry/180.0f));
	
	
  	glMultMatrixd(modMat);
	
        //glActiveTextureARB(GL_TEXTURE0_ARB);

        

       
   
        //glutSolidTorus(3.0,10.0,8,16);
	foreach(Triangles* curModel, TriangleModels)
	{
		this->renderTriangleModel(curModel);
	}

	/*foreach(Polygons* curModel, PolygonModels)
	{
		this->renderPolygonModel(curModel);
	}
	*/ 
	if(showModel)
	{
		this->renderPolygonModel(PolygonModels.at(1));
	}
	else{
		this->renderPolygonModel(PolygonModels.at(0));

	}
		
	foreach(Nurbs* curModel, NurbsModels)
	{
		this->renderNurbsModel(curModel);
	}

	// Store the rotation.
	glLoadIdentity();
	glRotatef(rtry,1.0f,0.0f,0.0f);	
	glRotatef(rtrx,0.0f,cos(3.141*rtry/180.0f),sin(3.141*rtry/180.0f));
  	glMultMatrixd(modMat);
	glGetDoublev(GL_MODELVIEW_MATRIX,modMat);

	rtrx = 0.0;
	rtry = 0.0;
	//shift = 0.0;


        glFlush();		// Needed ?

        SDL_GL_SwapBuffers();
}





int gmGLDisplay::handleInput()
{
// !TODO enable rotation etc.
	
        while( SDL_PollEvent( &event ) 	) {
                if ( event.type == SDL_QUIT ) return 1;
		if(event.type == SDL_MOUSEBUTTONDOWN)
		{
			ModeMouseClicked = true;
		}
		if(event.type == SDL_MOUSEBUTTONUP)
		{
			ModeMouseClicked = false;
		}
		if(event.type ==  SDL_MOUSEMOTION && ModeMouseClicked){
	  
			rtry += event.motion.yrel; //this->testCamera.RotateX(event.motion.yrel);
			rtrx += event.motion.xrel; // this->testCamera.RotateY(event.motion.xrel);
		}
		if ( event.type == SDL_KEYDOWN ) {
			if(event.key.keysym.sym == SDLK_c) {
				ctrlPnts = (!ctrlPnts);
			}
			if(event.key.keysym.sym == SDLK_m){
				showModel = (!showModel);
			}
			//rtri += 10;
			if ( event.key.keysym.sym == SDLK_UP )
			{
			fwd = true;
			bwd = false;

			}
			if ( event.key.keysym.sym == SDLK_DOWN ){
			bwd = true;
			fwd = false;
			}
		}
		if ( event.type == SDL_KEYUP ) {
			
			if(event.key.keysym.sym == SDLK_UP || event.key.keysym.sym == SDLK_DOWN)
			{
			bwd = false;
			fwd = false;
			stage = 1.0f;
			}
		}
			
	}
        return 0;
}

int last_time = 0; // time in ms since start
int Ctime = 0; // The time passed while rendering the last frame
int tmp = 0;
float fps = 0;
float avg = 0;
int delta = 0;

void gmGLDisplay::mainloop()
{


        while ( handleInput() == 0 ) {
                tmp = SDL_GetTicks();
                Ctime = tmp - last_time;
                last_time = tmp;
                fps = 1000.0f/Ctime;
                if (avg == 0) avg = 1000.0f/Ctime;
                else 	avg = (avg+fps)/2.0f;
                delta = 20 - Ctime;
                if (delta > 0 && delta < 20) {
                        SDL_Delay(delta);
                }
		if(fwd){
			shift += stage*0.5f;
			stage += 0.1f;
		}
		if(bwd){
			shift -= stage*0.5f;
			stage += 0.1f;
		}
                this->draw();
        }

        cout << endl;
        SDL_Quit();


}

void gmGLDisplay::display(Polygons* Model)
{
	this->PolygonModels.push_back(Model);
}

void gmGLDisplay::display(Triangles* Model)
{
	this->TriangleModels.push_back(Model);
}

void gmGLDisplay::display(Nurbs* Model)
{
	this->NurbsModels.push_back(Model);
	theNurbs = gluNewNurbsRenderer();

	
}

void gmGLDisplay::renderTriangleModel(Triangles* Model)
{
	//glEnable( GL_NORMALIZE );
  	//glEnable( GL_AUTO_NORMAL );
	glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
	glColor3f(0.4f,0.4f,1.0f);
	boost::shared_ptr<vector<Vector3d> > VertexCoordinates = Model->getVertexCoordinates();
	boost::shared_ptr<vector<Vector3d> > Normals = Model->getNormals();
	foreach(Triangle curTriangle, *(Model->getTriangleList()) )
	{
	
	glColor4f(0.0f,0.4f,0.4f,0.f);
	glBegin(GL_TRIANGLES);
	//glBegin(GL_LINE_LOOP);
	
		int k = 0;
		foreach(int i, curTriangle.Vertices)
		{
			glColor4f(0.0f,0.5f,0.5f,0.0f);
			if(k == 0)
			//glColor4f(1.0,0.0,0.0,1.0f);
			if(k==1)
			//glColor3f(0.0,1.0,0.0);
			if(k==2)
			//glColor3f(0.0f,0.0f,1.0f);
			

			i = curTriangle.Vertices.at(k);
			if(i < VertexCoordinates->size() )
			glVertex3f(VertexCoordinates->at(i).x,VertexCoordinates->at(i).y,VertexCoordinates->at(i).z);
			
			
			if(k < curTriangle.Normals.size() )
			{
			int j = curTriangle.Normals.at(k);
			if(j < Normals->size()); glNormal3f(Normals->at(j).x,Normals->at(j).y,Normals->at(j).z);
			
			} 
			k++;
		}
	glEnd();
	
	}
		
	
}

void gmGLDisplay::renderPolygonModel(Polygons* Model)
{
	
	boost::shared_ptr<vector<Vector3d> > VertexCoordinates = Model->getVertexCoordinates();
	boost::shared_ptr<vector<Vector3d> > Normals = Model->getNormals();
	int max = 0;
	int min = 9999;
	foreach(Polygon curPolygon, *(Model->getPolygonList()) )
	{
		glBegin(GL_LINE_LOOP);
		int k = 0;
		//if(curPolygon.Vertices.size() != 3) cout << "Alert" << 	endl;
		foreach(int i, curPolygon.Vertices)
		{
			
			
			if((i) < VertexCoordinates->size()){
			glVertex3f(VertexCoordinates->at(i).x,VertexCoordinates->at(i).y,VertexCoordinates->at(i).z);
			if(min > i){
			 min = i;
			//cout << "min: "<<min << endl;
			}
			}
			if(i >= VertexCoordinates->size()){
			if (i>max) max = i;
			// cout << "max: " << max << endl;
			}

			if(k < curPolygon.Normals.size() )
			{
			int j = curPolygon.Normals.at(k);
			if(j-1 < Normals->size())
			glNormal3f(Normals->at(j).x,Normals->at(j).y,Normals->at(j).z);
			}
			k++;
		}
		
		
		glEnd();
	}
		
	
}

void gmGLDisplay::renderNurbsModel(Nurbs* Model)
{
	PLib::NurbsSurfacef* myNurbs =  Model->getNurbs();
	PLib::NurbsSurfacef surface = *myNurbs;

	GLfloat v[ 4 ];
	int display_mode = GLU_FILL;
  gluNurbsProperty( theNurbs, ( GLenum ) GLU_DISPLAY_MODE, display_mode );

  glEnable( GL_NORMALIZE );
  glEnable( GL_AUTO_NORMAL );
  glColor3f(214.0f/255.0f	,214.0f/255.0f,240.0f/255.0f);  

	if(showModel)
	{
gluBeginSurface( theNurbs );
  
  gluNurbsSurface( theNurbs, surface.knotU().n(), surface.knotU().memory(),
                   surface.knotV().n(), surface.knotV().memory(),
                   4,
                   4 * surface.ctrlPnts().rows(),
                   ( surface.ctrlPnts() ) [ 0 ] ->data,
                   surface.degreeU() + 1,
                   surface.degreeV() + 1,
                   GL_MAP2_TEXTURE_COORD_2 );

  gluNurbsSurface( theNurbs, surface.knotU().n(), surface.knotU().memory(),
                   surface.knotV().n(), surface.knotV().memory(),
                   4,
                   4 * surface.ctrlPnts().rows(),
                   ( surface.ctrlPnts() ) [ 0 ] ->data,
                   surface.degreeU() + 1,
                   surface.degreeV() + 1,
                   GL_MAP2_VERTEX_4 );

}

if(ctrlPnts)
{
	glBegin(GL_POINTS);
 for(int i = 0; i < surface.ctrlPnts().rows();i++)
{
	for(int j = 0; j < 4;j++)
	{
		float w = surface.ctrlPnts(i,j).w();
		float x = surface.ctrlPnts(i,j).x()/w;
		float y = surface.ctrlPnts(i,j).y()/w;
		float z = surface.ctrlPnts(i,j).z()/w;
		glVertex3f(x,y,z );
		
	}
}
	glEnd();

}
/*
glBegin(GL_POINTS);
	for(int i = 0; i < surface.knotU.n(); i++)
	{
		float w = surface.knotU(i).w();
		float x = surface.knotU(i).x()/w;
		float y = surface.knotU(i).y()/w;
		float z = surface.knotU(i).z()/w;
	}


glEnd();
*/

  gluEndSurface( theNurbs );
  glDisable( GL_NORMALIZE );

	
}

int gmGLDisplay::init()
{
        cout << "Starting gmDisplay... " << endl ;
	
        gmDisplay* Display = new gmDisplay();
        Display->init(1024, 768, 24);

        // SETUP OPENGL
        //glEnable(GL_TEXTURE_2D);						
        glShadeModel(GL_SMOOTH);						
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f);					
        glClearDepth(1.0f);							
        //glEnable(GL_DEPTH_TEST);						
        //glDepthFunc(GL_LEQUAL);							
        glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);			

        //glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
       // glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
       // glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
       // glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
       //glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
       // glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
        glPolygonMode (GL_FRONT, GL_FILL);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);


        SDL_WM_GrabInput(SDL_GRAB_OFF);
        SDL_ShowCursor(SDL_ENABLE);

	

	
        int i = 0;
        char* A = "";
        //glutInit(&i, &A);

        last_time = SDL_GetTicks();
	glLoadIdentity();
	glGetDoublev(GL_MODELVIEW_MATRIX,modMat);


	rtrx = 0;
	rtry = 0;
	
	ModeMouseClicked = false;
	shift = -80.0f;
	stage = 0.0f;
	glLoadIdentity();
	glGetDoublev(GL_MODELVIEW_MATRIX,modMat);

        mainloop();
        return 0;

}
